#include <os/fs.h>
#include <os/mm.h>
#include <os/string.h>
#define FOLDER_MODE 12
#define FILE_MODE 15
superblock_t superblock={
	.magic = FS_MAGIC,
	.used_size = 0,
	.Inode_Num = 1024,
	.DataBlock_Num = (1<<20),
	.InodeMap_Entry = FS_START_ADDR+SUPERBLOCK_SIZE+BLOCKMAP_SIZE,
	.BlockMap_Entry = FS_START_ADDR+SUPERBLOCK_SIZE,
	.InodeTable_Entry = FS_START_ADDR+SUPERBLOCK_SIZE+BLOCKMAP_SIZE+INODEMAP_SIZE,
	.DataBlock_Entry = FS_START_ADDR+SUPERBLOCK_SIZE+BLOCKMAP_SIZE+INODEMAP_SIZE+INODETABLE_SIZE
};

int free_disk_sector;
char sector_buffer[512];
int get_attribute(char data,int position){
	//position 0,1,2,3,4,5,6,7
	//get i th attributes of a char type data
	char mask = 1;
	mask = mask<<position;
	if((data & mask)!= 0)
		return 1;
	return 0;
}
int set_attribute(char *data,int position){
	//position 0,1,2,3,4,5,6,7
	//set i th attributes of a char type data
	char mask=1;
	mask = 1 << position;
	*data = (*data) | mask;
	return data;
}
int alloc_data_block(int map_entry){
	int i = 0;
	while(i<BLOCKMAP_SIZE){
		char buffer[512];
		memset(buffer,0,512);
		sbi_sd_read(buffer,1,map_entry+i);
		for(int j=0;j<512;j++)
			for(int k=0;k<8;k++)
				if(get_attribute(buffer[j],k)== 0){
					set_attribute(&buffer[j],k);
					sbi_sd_write(buffer,1,map_entry+i);
					memset(buffer,0,512);
					sbi_sd_write(buffer,1,512*8*i+8*j+k+superblock.DataBlock_Entry);
					return 512*8*i+8*j+k+superblock.DataBlock_Entry;
				}
	}
}
int alloc_inode(int map_entry){
	int i = 0;
	//return the inode index
	while(i<INODEMAP_SIZE){
		char buffer[512];
		memset(buffer,0,512);
		sbi_sd_read(buffer,1,map_entry+i);
		for(int j=0;j<512;j++){
			for(int k=0;k<8;k++){
				if(get_attribute(buffer[j],k)== 0){
					set_attribute(&buffer[j],k);
					sbi_sd_write(buffer,1,map_entry+i);
					return 512*8*i+8*j+k;
				}
			}
		}
	}
}
int find_inode_sector(int index,char *buffer,int *block_id_ptr){
	memset(buffer,0,512);
	int i=0;
	while(index > 9){
		index-=9;
		i++;
	}
	sbi_sd_read(buffer,1,superblock.InodeTable_Entry+i);
	*block_id_ptr = superblock.InodeTable_Entry+i;
	return index;
}
int set_inode(int mode,int size,int index,int table_entry){
	char buffer[512];
	int block_id=0;
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index; 
	ptr->mode = mode;
	ptr->file_size = size;
	if(size > 10*512){
		prints("File size is too large!\n");
		return -1;
	}
	int j=0;
	/*while(size>0){	
		ptr->DirctIndex[j] = alloc_data_block(superblock.BlockMap_Entry);
		j++;
		size-=512;
	}*/
	sbi_sd_write(buffer,1,block_id);
}
int set_dentry(char *dir_name,int link_inode,int dentry_indode){
	char buffer[512];
	int block_id=0;
	int offset = find_inode_sector(dentry_indode,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr += offset;
	dentry_t tmp;
	memcpy(&(tmp.filename),dir_name,strlen(dir_name)+1);
	tmp.ino = link_inode;
	tmp.used = 1;
	int flag = -1;
	if(ptr->mode == FOLDER_MODE ){
		char dentry_buffer[512];
		memset(dentry_buffer,0,512);
		if(ptr->file_size == 0){
			ptr->DirctIndex[0] = alloc_data_block(superblock.BlockMap_Entry);
			sbi_sd_write(dentry_buffer,1,ptr->DirctIndex[0]);
			ptr->file_size +=512;
		}
		sbi_sd_read(dentry_buffer,1,ptr->DirctIndex[0]);
		dentry_t *table = (dentry_t *)dentry_buffer;
		while(table->used == 1)
			table++;
		*table = tmp;
		sbi_sd_write(dentry_buffer,1,ptr->DirctIndex[0]);
		flag =1;
	}
	sbi_sd_write(buffer,1,block_id);
	return flag;
}
int do_mkfs(){
	int success = 1;
	superblock_t * super_ptr;
	super_ptr = (superblock_t *)kmalloc_Page();
	sbi_sd_read(super_ptr,1,FS_START_ADDR);

	if(super_ptr->magic != FS_MAGIC){
		sbi_sd_write(&superblock,1,FS_START_ADDR);
		super_ptr = &superblock;
		memset(sector_buffer,0,512);
		for (int i = 0; i < BLOCKMAP_SIZE; ++i)
			sbi_sd_write(sector_buffer,1,super_ptr->BlockMap_Entry+i);
		for(int i=0;i<INODEMAP_SIZE;i++)
			sbi_sd_write(sector_buffer,1,super_ptr->InodeMap_Entry+i);
		success = 0;
		//make root dir
		//free_disk_sector = super_ptr->DataBlock_Entry;
		int root_inode = alloc_inode(super_ptr->InodeMap_Entry);
		set_inode(FOLDER_MODE,0,root_inode,super_ptr->InodeTable_Entry);
		char father[]="..";
		char now[]=".";
		set_dentry(father,root_inode,root_inode);
		set_dentry(now,root_inode,root_inode);
		//how to mk root dir
		//need "write_unaligned_sector"
		//need "alloc_inode"
		//need "alloc_data_block"
	}
	prints("[FS] magic : 0x%lx\n",super_ptr->magic);
		prints("     used_size : %ld bytes\n",super_ptr->used_size);
		prints("     inode number : %d\n",super_ptr->Inode_Num);	
		prints("     block number : %d\n",super_ptr->DataBlock_Num);
		prints("     inode map offset : %d\n",super_ptr->InodeMap_Entry);
		prints("     block map offset : %d\n",super_ptr->BlockMap_Entry);
		prints("     inode table offset : %d\n",super_ptr->InodeTable_Entry);
		prints("     data entry : %d\n",super_ptr->DataBlock_Entry);
	return success;
}


int do_fs_show(){
	char sector_buffer[512];
	sbi_sd_read(sector_buffer,1,FS_START_ADDR);
	superblock_t *ptr;
	ptr = (superblock_t *)sector_buffer;
	if(ptr->magic!=FS_MAGIC)
		return 0;
	superblock_t tmp = *ptr;
	ptr = &tmp;
	int used_inode_num=0;
for(int k=0;k<INODEMAP_SIZE;k++){
	sbi_sd_read(sector_buffer,1,ptr->InodeMap_Entry+k);
	for(int i=0;i<512;i++){
		for(int j=0;j<8;j++)
			used_inode_num+=get_attribute(sector_buffer[i],j);
	}
}
	int used_block_num=0;
for(int k=0;k<BLOCKMAP_SIZE;k++){
	memset(sector_buffer,0,512);
	sbi_sd_read(sector_buffer,1,ptr->BlockMap_Entry+k);
	for(int i=0;i<512;i++){
		for(int j=0;j<8;j++)
			used_block_num+=get_attribute(sector_buffer[i],j);
	}
}
	prints("[FS] magic : 0x%lx\n",ptr->magic);
	prints("     used inode num: %d/%d\n",used_inode_num,ptr->Inode_Num);
	prints("     used block num: %d/%d\n",used_block_num,ptr->DataBlock_Num);
	prints("     inode map offset : %d\n",ptr->InodeMap_Entry);
	prints("     block map offset : %d\n",ptr->BlockMap_Entry);
	prints("     inode table offset : %d\n",ptr->InodeTable_Entry);
	prints("     data entry : %d\n",ptr->DataBlock_Entry);
	return 1;
}
int free_inode(int index){
	char buffer[512];
	sbi_sd_read(buffer,1,superblock.InodeMap_Entry);
	set_attribute(&buffer[index/8],index%8);
	sbi_sd_write(buffer,1,superblock.InodeMap_Entry);
	return 1;
}
int free_dentry(char * dir_name,int dentry_indode){
	//return linked_inode
	char buffer[512];
	memset(buffer,0,512);
	int index = dentry_indode;
	int block_id = 0;
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	inode_t tmp = *ptr;
	ptr = &tmp;
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	dentry_t * dir=(dentry_t*)buffer;
	int i=0;
	while(dir[i].used == 1){
		if(strcmp(dir[i].filename,dir_name) == 0){
			dir[i].filename[0] = '\0';
			dir[i].used = 0;
			int old = dir[i].ino;
			dir[i].ino = 0;
			sbi_sd_write(buffer,1,ptr->DirctIndex[0]);
			return old;
		}
		i++;
	}
	return -1;
}
int do_mkdir(char * dir_name,int current_inode_index){
	int success = -1;
	int new_inode = alloc_inode(superblock.InodeMap_Entry);
	set_inode(FOLDER_MODE,0,new_inode,superblock.InodeTable_Entry);
	success = set_dentry(dir_name,new_inode,current_inode_index);
	char father[]="..";
	char now[]=".";
	set_dentry(father,current_inode_index,new_inode);
	set_dentry(now,new_inode,new_inode);
	return success;
}
int do_rmdir(char * dir_name,int current_inode_index){
	int success = -1;
	int now = free_dentry(dir_name,current_inode_index);
	if(now == -1)
		return -1;
	success = free_inode(now);
	return success;
}

void do_show_dir(int current_inode_index){
	char buffer[512];
	memset(buffer,0,512);
	int block_id = 0;
	int index = current_inode_index;
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index; 
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	dentry_t * dir=(dentry_t*)buffer;
	int i=0;
	while(dir[i].used == 1){
		prints("%s ",dir[i].filename);
		i++;
	}
	prints("\n");
}
int current_path[256]={0,-1};
int do_change_dir(char *dir_name,int path[],int argc,char *argv[]){
	int success=-1;
	int i=0;
	while(path[i]!=-1){
		current_path[i]=path[i];
		i++;
	}
	current_path[i]=-1;
	if(strcmp(dir_name,".")==0)
		return 1;
	if(strcmp(dir_name,"..")==0){
		if(path[i-1]==0)
			return 1;
		else{
			path[i-1]=-1;
			current_path[i-1]=-1;
			return 1;
		}
	}
	char buffer[512];
	int block_id=0;
	int index = path[i-1];
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	inode_t tmp = *ptr;
	ptr = &tmp;
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	dentry_t *table = (dentry_t *)buffer;
	int j=0;
	while(table[j].used == 1){
		if(strcmp(table[j].filename,dir_name)==0){
			path[i]=table[j].ino;
			path[i+1] = -1;
			current_path[i]=path[i];
			current_path[i+1] = -1;
			return 1;
		}
		j++;
	}
	//return -1;
	return success;
}

int do_fopen(const char *file_name, int mode){
	//return the index of fd
	int i=0;
	while(current_path[i]!=-1)
		i++;
	char buffer[512];
	int block_id=0;
	int index = current_path[i-1];
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	inode_t tmp = *ptr;
	ptr = &tmp;
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	dentry_t *table = (dentry_t *)buffer;
	int j=0;
	while(table[j].used == 1){
		if(strcmp(table[j].filename,file_name)==0){
			fd_t temp;
			temp.ino = table[j].ino;
			temp.authority=mode;
			temp.wpos=0;
			temp.rpos=0;
			temp.used=1;
			int k=0;
			while(current_running->open_file[k].used!=0)
				k++;
			current_running->open_file[k]=temp;
			return k;
			//return ;
		}
		j++;
	}
	return -1;
}
int do_fwrite(int fd ,char *fw_buffer, int length){
	//return data size (written in file)
	int file_inode = current_running->open_file[fd].ino;
	char buffer[512];
	int block_id=0;
	int index = file_inode;
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	//inode_t tmp = *ptr;
	//ptr = &tmp;
	int pos = current_running->open_file[fd].wpos;
	//suppose small file
	int k=0;
	/*if(length+pos > 512-(ptr->file_size)%512){
		k = (ptr->file_size + length)/512;
	}*/
	int size = ptr->file_size;
	if(size/512 < (size+length)/512 || size == 0)
		ptr->DirctIndex[k] = alloc_data_block(superblock.BlockMap_Entry);
	ptr->file_size+=length;
	int wb_id = ptr->DirctIndex[k];
	sbi_sd_write(buffer,1,block_id);
	memset(buffer,0,512);
	sbi_sd_read(buffer,1,wb_id);
	for(int i=size%512;i<length+size%512;i++)
		buffer[i]=fw_buffer[i-size%512];
	current_running->open_file[fd].wpos +=length;
	sbi_sd_write(buffer,1,wb_id);
	//find file's inode 
	//find file's data block
	return length;

}
int do_fread(int fd, char *fr_buffer, int length){
	//return data size (written in buffer)
	int file_inode = current_running->open_file[fd].ino;
	char buffer[512];
	int block_id=0;
	int index = file_inode;
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	int pos = current_running->open_file[fd].rpos;
	//suppose small file
	int k=pos/512;
	sbi_sd_read(buffer,1,ptr->DirctIndex[k]);
	for(int i=pos;i<pos+length;i++){
		fr_buffer[i-pos] = buffer[i];
	}
	return length;
}
int do_fclose(int fd){
	int success=1;
	current_running->open_file[fd].used = 0;
	current_running->open_file[fd].authority = 0;
	current_running->open_file[fd].wpos = 0;
	current_running->open_file[fd].rpos = 0;

	return success;
}	

void do_touch(char *file_name){
	int i=0;
	while(current_path[i]!=-1)
		i++;
	int file_inode = alloc_inode(superblock.InodeMap_Entry);
	set_inode(FILE_MODE,0,file_inode,superblock.InodeTable_Entry);
	set_dentry(file_name,file_inode,current_path[i-1]);
	//find folder inode
	//create new inode
	//set dentry
}

void do_cat(char *file_name){
	int i=0;
	while(current_path[i]!=-1)
		i++;
	char buffer[512];
	int block_id=0;
	int index = current_path[i-1];
	index=find_inode_sector(index,buffer,&block_id);
	inode_t *ptr = (inode_t *)buffer;
	ptr = ptr+index;
	inode_t tmp = *ptr;
	ptr = &tmp;
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	dentry_t *table = (dentry_t *)buffer;
	int j=0;
	int file_inode = 0;
	while(table[j].used == 1){
		if(strcmp(table[j].filename,file_name)==0){
			file_inode = table[j].ino;
		}
		j++;
	}
	index = file_inode;
	index=find_inode_sector(index,buffer,&block_id);
	ptr = (inode_t *)buffer;
	ptr = ptr+index;
	tmp = *ptr;
	ptr = &tmp;
	sbi_sd_read(buffer,1,ptr->DirctIndex[0]);
	prints("%s\n",buffer);
}